/*   */
header
{
  // usings here...
  using ACS.Compiler.Tree.Impl;
  using ACS.Compiler.Tree;
}

options 
{
    language = "CSharp";
    namespace = "ACS.Compiler.Grammar.Impl";
    
}

class AcsParser extends Parser;
options
{
    k=1;
    buildAST = true;
    ASTLabelType = "ACS.Compiler.Tree.TreeBase";
}

tokens { 
        MODULE        <AST = ACS.Compiler.Tree.Impl.Module>;
        TYPE          <AST = ACS.Compiler.Tree.Impl.TypeDef>;
        TYPEFIELD     <AST = ACS.Compiler.Tree.Impl.TypeField>;
        VOIDTYPE      <AST = ACS.Compiler.Tree.Impl.VoidTypeDeclarer>;
        SIMPLETYPE    <AST = ACS.Compiler.Tree.Impl.SimpleTypeDeclarer>;
        STRUCTTYPE    <AST = ACS.Compiler.Tree.Impl.StructTypeDeclarer>;
        STRUCTTYPE1   <AST = ACS.Compiler.Tree.Impl.StructType1Declarer>;
        DATA          <AST = ACS.Compiler.Tree.Impl.Data>;
        TYPEVAR1      <AST = ACS.Compiler.Tree.Impl.FormalParameter>;  
        PROC          <AST = ACS.Compiler.Tree.Impl.Proc>;
        STATEMENTS    <AST = ACS.Compiler.Tree.Impl.Block>;
        CALL          <AST = ACS.Compiler.Tree.Impl.Call>;
        ASSIGN        <AST = ACS.Compiler.Tree.Impl.Assign>;
        RETURN        <AST = ACS.Compiler.Tree.Impl.Return>;
        CALLPARAM     <AST = ACS.Compiler.Tree.Impl.CallParameter>;
        SELECT        <AST = ACS.Compiler.Tree.Impl.Select>;
        SLICE         <AST = ACS.Compiler.Tree.Impl.Slice>;
        ID            <AST = ACS.Compiler.Tree.Impl.Ident>;
        EMPTYINDEX    <AST = ACS.Compiler.Tree.Impl.EmptyIndex>;
        SCONST        <AST = ACS.Compiler.Tree.Impl.StringConst>;
        ICONST        <AST = ACS.Compiler.Tree.Impl.IntConst>;
        BCONST        <AST = ACS.Compiler.Tree.Impl.BoolConst>;
        STAR          <AST = ACS.Compiler.Tree.Impl.Star>;
        EQQ           <AST = ACS.Compiler.Tree.Impl.Eqq>;
        PLUS          <AST = ACS.Compiler.Tree.Impl.Plus>;
        ARROW         <AST = ACS.Compiler.Tree.Impl.Arrow>;
        IF            <AST = ACS.Compiler.Tree.Impl.If>;
        WHILE         <AST = ACS.Compiler.Tree.Impl.While>;
}

{
    private TreeBase _lastPrefix = null;
}

mprogram:
        (mtype)* (mdata)? (mproc)* mblock
        { ## = #([MODULE,"MODULE"], #mprogram); }
        ;

mtype!: 
         KEYWORD_TYPE! id:mvar LF! fields:typefields RF!
         { ## = #([TYPE,"TYPE"], #id, #fields); }
        ;

mdata!:
         KEYWORD_DATA^ LF! fields:typefields RF!
         { ## = #([DATA], #fields); }
        ;

typefields: 
          (decl SEMI!)+
        ;

decl!: def:mtypedef id:mvar
          { ## = #([TYPEFIELD], #id, #def); }
        ;
        
mtypevar1!:
        def:mtypedef1 id:mvar
                 {## = #([TYPEVAR1, "TYPEVAR1"], #id, #def );}
        ;

mtypedef!:
         k:KEYWORD_INT 
          {## = #([SIMPLETYPE], #k); }
       | k1:KEYWORD_BOOL 
          {## = #([SIMPLETYPE], #k1); }
       | k2:KEYWORD_STRING 
          {## = #([SIMPLETYPE], #k2); }
       | (i1:mvar (DOT! j1:mvar)? (LS! i2:INT RS!)?) 
          {## = #([STRUCTTYPE], #i1, #i2, #j1 ); }
        ;

mtypedef1:
         k:KEYWORD_INT 
          {## = #([SIMPLETYPE], #k); }
       | k1:KEYWORD_BOOL 
          {## = #([SIMPLETYPE], #k1); }
       | k2:KEYWORD_STRING 
          {## = #([SIMPLETYPE], #k2); }
       |! (i1:mvar (DOT j1:mvar)? (ind:LS RS )?) 
          {
            if (#ind == null) {
              ## = #([STRUCTTYPE1, "STRUCTTYPE1"], #i1, #j1 ); 
            } else { 
              ## = #([STRUCTTYPE1, "STRUCTTYPE1"], #i1, #([EMPTYINDEX]), #j1 ); 
            }
          }
        ;

mproc:
      mprocreturntype mvar LR! procparams RR! (mdata)? mblock
      {#mproc = #([PROC, "PROC"], #mproc);}
        ;   

procparams: (mtypevar1 (COMMA! mtypevar1)* )?
       ;

mprocreturntype: mtypevoid
       ;

mtypevoid: mtypedef1 
          |! k:KEYWORD_VOID
          {## = #([VOIDTYPE], #k); }
        ;

mvar:  ID
       ;

mblock: LF! (mstmt)* RF!
       { ## = #([STATEMENTS, "STATEMENTS"], #mblock); }
        ;

mstmt: mblock |
       callorassign |
       mreturn |
       mwhile | 
       mif
   ;

mparameter!: e:mexpr
            {
             ## = #([CALLPARAM], #e);
            }
            ;

mparameters: (mparameter (COMMA! mparameter)* )?
          ;

callorassign!: p:prefix 
             {
              AST lastPrefix = _lastPrefix;
              _lastPrefix = null;
             } (
             ! LR! par:mparameters RR! SEMI! 
             { 
                 ##= #([CALL, "CALL"], #p, #par); 
             }
          |  ! (ex:mindex (tail:suffix)? )?  EQ! e:mexpr SEMI! 
             {
               TreeBase temp = null;
               if ( #ex != null )  {
                 if (lastPrefix == null) {                     
                   temp =  #([SLICE, "SLICE"], #p, #ex);
                   if (#tail != null) {
                     temp =  #([SELECT, "SELECT"], temp, #tail);
                   }
                 } else {                  
                   temp = #([SLICE, "SLICE"], lastPrefix.getNextSibling(), #ex);
                   if (#tail != null) { 
                     temp =  #([SELECT, "SELECT"], temp, #tail);
                   }  
                   lastPrefix.setNextSibling(temp);
                   temp = #p;
                 }
               } else {
                 temp = #p;
               }
               ## = #([ASSIGN, "ASSIGN"], temp, #e); 
             }
          )     
        ;

callorident!:
            p: prefix 
            {
              TreeBase lastPrefix = _lastPrefix;
              _lastPrefix = null;

              ## = #p;
            } (
            ! (ex:mindex (tail:suffix)? )?  
             {
               TreeBase temp = null;
               if ( #ex != null )  {
                 if (lastPrefix == null) {                     
                   temp =  #([SLICE, "SLICE"], #p, #ex);
                   if (#tail != null) {
                     temp =  #([SELECT, "SELECT"], temp, #tail);
                   }
                 } else {
                   temp = #([SLICE, "SLICE"], lastPrefix.getNextSibling(), #ex);
                   if (#tail != null) { 
                     temp =  #([SELECT, "SELECT"], temp, #tail);
                   }  
                   lastPrefix.setNextSibling(temp);
                   temp = #p;
                 }
               } else {
                 temp = #p;
               }
               ## = temp; 
             }
          |! ( LR! par:mparameters RR! ) (s:suffix)?
           {
             if (#s == null) {
               ## = #([CALL, "CALL"], #p, #par); 
             } else {
               ## = #([SELECT, "SELECT"], #([CALL, "CALL"], #p, #par), #s); 
             }
           }
          )?
          ;

prefix!: 
         left:mvar (DOT! {_lastPrefix = #left;} right:prefix)?
         { 
           if ( #right != null ) {             
             ## = #([SELECT,"SELECT"], #left, #right );
           } else {
             ## = #left;
           } 
         }
         ;

suffix!: DOT! id:mvar (LS! ind:mexpr RS!)? (s:suffix)? 
         {
           if ( #s != null ) {
             if (#ind != null) {
               ## = #([SELECT,"SELECT"], #([SLICE, "SLICE"], #id, #ind), #s );               
             } else {
               ## = #([SELECT,"SELECT"], #id, #s );               
             }
           } else {
             if (#ind != null) {
               ## = #([SLICE, "SLICE"], #id, #ind);
             } else {
               ## = #id;
             }
           } 
         }
         ;

mindex: LS! ind:mexpr RS!
         ;

mreturn!: KEYWORD_RETURN (e:mexpr)? SEMI
         { ## = #([RETURN], #e );  }
     ;

mwhile: KEYWORD_WHILE! LR! mexpr RR! mblock
       {
         ## = #([WHILE], #mwhile); 
       }
       ;

mif: KEYWORD_IF! LR! mexpr RR! mblock (KEYWORD_ELSE! mblock)?
       {
         ## = #([IF], #mif); 
       }
        ;

mexpr: m1mexpr (EQQ^ m1mexpr)*
        ;

m1mexpr: mmexpr (PLUS^ mmexpr)*
        ;

mmexpr: e1:mbexpr (STAR^ e2:mbexpr)*      
        ;

mbexpr: e1:maexpr (ARROW^ e2:maexpr)*      
        ;

maexpr: 
       (
         callorident
       |! e:INT
         { ## = #([ICONST], #e); }
       | LR! mexpr RR!
       |! s:STRING 
         { ## = #([SCONST], #s); }
       |! b1:KEYWORD_TRUE 
         { ## = #([BCONST], #b1); }
       |! b2:KEYWORD_FALSE 
         { ## = #([BCONST], #b2); }
       )
       ;
          


class AcsLexer extends Lexer;
options
{
    k=2;
}

tokens 
{
  KEYWORD_TYPE="type";
  KEYWORD_DATA="data";
  KEYWORD_INT="int";
  KEYWORD_BOOL="bool";
  KEYWORD_STRING="string";
  KEYWORD_VOID="void";
  KEYWORD_IF="if";
  KEYWORD_ELSE="else";
  KEYWORD_WHILE="while";
  KEYWORD_TRUE="true";
  KEYWORD_FALSE="false";
  KEYWORD_RETURN="return";
}


LF: '{' ;
RF: '}' ;

LS: '[' ;
RS: ']' ;

LR: '('
        ;

RR: ')'
        ;

STAR:   '*'
        ;

ARROW:   '>'
        ;

EQQ:   "=="
        ;

PLUS:   '+'
        ;

SEMI:   ';'
        ;

COMMA: ','
        ;

EQ:    '='
       ;

DOT:     '.'
       ;
protected
DIGIT: '0'..'9'
        ;

protected
LETTER: ('a'..'z') | ('A'..'Z')
             
        ;
INT: (DIGIT)+
        ;

STRING: '"' ( ESCAPE | ~('"'|'\\') )* '"' 
          ;

protected
ESCAPE
    :    '\\'
         ( 'n' { $setText("\n"); }
         | 'r' { $setText("\r"); }
         | 't' { $setText("\t"); }
         | '"' { $setText("\""); }
         )
    ;

ID: LETTER (LETTER | DIGIT)*;


SL_COMMENT:
       "//"
                (~('\n'|'\r'))* ('\n'|'\r'('\n')?)?
                {$setType(Token.SKIP); newline();}
        ;

WS      :       (       ' '
                |       '\t'
                |       '\f'
                        // handle newlines
                |       (       options {generateAmbigWarnings=false;}
                        :       "\r\n"  // Evil DOS
                        |       '\r'    // Macintosh
                        |       '\n'    // Unix (the right way)
                        )
                        { newline(); }
                )+
                { _ttype = Token.SKIP; }
        ;

